/* keydb.c - Key database routines
 * Copyright (C) 2002-2012 Free Software Foundation, Inc.
 *
 * Author: Timo Schulz
 *
 * This file is part of OpenCDK.
 *
 * The OpenCDK library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "opencdk.h"
#include "main.h"
#include "packet.h"
#include "filters.h"
#include "stream.h"
#include "keydb.h"

#define KEYID_CMP(a, b) ((a[0]) == (b[0]) && (a[1]) == (b[1]))
#define KEYDB_CACHE_ENTRIES 8

static void keydb_cache_free (key_table_t cache);
static int classify_data (const byte * buf, size_t len);
static cdk_kbnode_t find_selfsig_node (cdk_kbnode_t key, cdk_pkt_pubkey_t pk);

static char *
keydb_idx_mkname (const char *file)
{
  static const char *fmt = "%s.idx";
  char *fname;
  size_t len = strlen (file) + strlen (fmt);

  fname = cdk_calloc (1, len + 1);
  if (!fname)
    return NULL;
  if (snprintf (fname, len, fmt, file) <= 0)
    return NULL;
  return fname;
}


/* This functions builds an index of the keyring into a separate file
   with the name keyring.ext.idx. It contains the offset of all public-
   and public subkeys. The format of the file is:
   --------
    4 octets offset of the packet
    8 octets keyid
   20 octets fingerprint
   --------
   We store the keyid and the fingerprint due to the fact we can't get
   the keyid from a v3 fingerprint directly.
*/
static cdk_error_t
keydb_idx_build (const char *file)
{
  cdk_packet_t pkt;
  cdk_stream_t inp, out = NULL;
  byte buf[4 + 8 + KEY_FPR_LEN];
  char *idx_name;
  u32 keyid[2];
  cdk_error_t rc;

  if (!file)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  rc = cdk_stream_open (file, &inp);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  idx_name = keydb_idx_mkname (file);
  if (!idx_name)
    {
      cdk_stream_close (inp);
      gnutls_assert ();
      return CDK_Out_Of_Core;
    }
  rc = cdk_stream_create (idx_name, &out);
  cdk_free (idx_name);
  if (rc)
    {
      cdk_stream_close (inp);
      gnutls_assert ();
      return rc;
    }

  cdk_pkt_new (&pkt);
  while (!cdk_stream_eof (inp))
    {
      off_t pos = cdk_stream_tell (inp);

      rc = cdk_pkt_read (inp, pkt);
      if (rc)
        {
          _cdk_log_debug ("index build failed packet off=%lu\n", (unsigned long)pos);
          /* FIXME: The index is incomplete */
          break;
        }
      if (pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
          pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY)
        {
          _cdk_u32tobuf (pos, buf);
          cdk_pk_get_keyid (pkt->pkt.public_key, keyid);
          _cdk_u32tobuf (keyid[0], buf + 4);
          _cdk_u32tobuf (keyid[1], buf + 8);
          cdk_pk_get_fingerprint (pkt->pkt.public_key, buf + 12);
          cdk_stream_write (out, buf, 4 + 8 + KEY_FPR_LEN);
        }
      cdk_pkt_free (pkt);
    }

  cdk_pkt_release (pkt);

  cdk_stream_close (out);
  cdk_stream_close (inp);
  gnutls_assert ();
  return rc;
}


/**
 * cdk_keydb_idx_rebuild:
 * @hd: key database handle
 *
 * Rebuild the key index files for the given key database.
 **/
cdk_error_t
cdk_keydb_idx_rebuild (cdk_keydb_hd_t db, cdk_keydb_search_t dbs)
{
  struct stat stbuf;
  char *tmp_idx_name;
  cdk_error_t rc;
  int err;

  if (!db || !db->name || !dbs)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (db->secret)
    return 0;

  tmp_idx_name = keydb_idx_mkname (db->name);
  if (!tmp_idx_name)
    {
      gnutls_assert ();
      return CDK_Out_Of_Core;
    }
  err = stat (tmp_idx_name, &stbuf);
  cdk_free (tmp_idx_name);
  /* This function expects an existing index which can be rebuild,
     if no index exists we do not build one and just return. */
  if (err)
    return 0;

  cdk_stream_close (dbs->idx);
  dbs->idx = NULL;
  if (!dbs->idx_name)
    {
      dbs->idx_name = keydb_idx_mkname (db->name);
      if (!dbs->idx_name)
        {
          gnutls_assert ();
          return CDK_Out_Of_Core;
        }
    }
  rc = keydb_idx_build (db->name);
  if (!rc)
    rc = cdk_stream_open (dbs->idx_name, &dbs->idx);
  else
    gnutls_assert ();
  return rc;
}


static cdk_error_t
keydb_idx_parse (cdk_stream_t inp, key_idx_t * r_idx)
{
  key_idx_t idx;
  byte buf[4];

  if (!inp || !r_idx)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  idx = cdk_calloc (1, sizeof *idx);
  if (!idx)
    {
      gnutls_assert ();
      return CDK_Out_Of_Core;
    }

  while (!cdk_stream_eof (inp))
    {
      if (cdk_stream_read (inp, buf, 4) == CDK_EOF)
        break;
      idx->offset = _cdk_buftou32 (buf);
      cdk_stream_read (inp, buf, 4);
      idx->keyid[0] = _cdk_buftou32 (buf);
      cdk_stream_read (inp, buf, 4);
      idx->keyid[1] = _cdk_buftou32 (buf);
      cdk_stream_read (inp, idx->fpr, KEY_FPR_LEN);
      break;
    }
  *r_idx = idx;
  return cdk_stream_eof (inp) ? CDK_EOF : 0;
}


static cdk_error_t
keydb_idx_search (cdk_stream_t inp, u32 * keyid, const byte * fpr,
                  off_t * r_off)
{
  key_idx_t idx;

  if (!inp || !r_off)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if ((keyid && fpr) || (!keyid && !fpr))
    {
      gnutls_assert ();
      return CDK_Inv_Mode;
    }

  /* We need an initialize the offset var with a value
     because it might be possible the returned offset will
     be 0 and then we cannot differ between the begin and an EOF. */
  *r_off = 0xFFFFFFFF;
  cdk_stream_seek (inp, 0);
  while (keydb_idx_parse (inp, &idx) != CDK_EOF)
    {
      if (keyid && KEYID_CMP (keyid, idx->keyid))
        {
          *r_off = idx->offset;
          break;
        }
      else if (fpr && !memcmp (idx->fpr, fpr, KEY_FPR_LEN))
        {
          *r_off = idx->offset;
          break;
        }
      cdk_free (idx);
      idx = NULL;
    }
  cdk_free (idx);
  return *r_off != 0xFFFFFFFF ? 0 : CDK_EOF;
}


/**
 * cdk_keydb_new_from_mem:
 * @r_hd: The keydb output handle.
 * @secret: does the stream contain secret key data
 * @armor: the stream is base64
 * @data: The raw key data.
 * @datlen: The length of the raw data.
 * 
 * Create a new keyring db handle from the contents of a buffer.
 */
cdk_error_t
cdk_keydb_new_from_mem (cdk_keydb_hd_t * r_db, int secret, int armor,
                        const void *data, size_t datlen)
{
  cdk_keydb_hd_t db;
  cdk_error_t rc;

  if (!r_db)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  *r_db = NULL;
  db = calloc (1, sizeof *db);
  rc = cdk_stream_tmp_from_mem (data, datlen, &db->fp);
  if (!db->fp)
    {
      cdk_free (db);
      gnutls_assert ();
      return rc;
    }

  if (armor)
    cdk_stream_set_armor_flag (db->fp, 0);
  db->type = CDK_DBTYPE_DATA;
  db->secret = secret;
  *r_db = db;
  return 0;
}

/**
 * cdk_keydb_free:
 * @hd: the keydb object
 *
 * Free the keydb object.
 **/
void
cdk_keydb_free (cdk_keydb_hd_t hd)
{
  if (!hd)
    return;

  if (hd->name)
    {
      cdk_free (hd->name);
      hd->name = NULL;
    }

  if (hd->fp && !hd->fp_ref)
    {
      cdk_stream_close (hd->fp);
      hd->fp = NULL;
    }


  hd->isopen = 0;
  hd->secret = 0;
  cdk_free (hd);
}


static cdk_error_t
_cdk_keydb_open (cdk_keydb_hd_t hd, cdk_stream_t * ret_kr)
{
  cdk_error_t rc;
  cdk_stream_t kr;

  if (!hd || !ret_kr)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  rc = 0;
  if ((hd->type == CDK_DBTYPE_DATA)
      && hd->fp)
    {
      kr = hd->fp;
      cdk_stream_seek (kr, 0);
    }
  else if (hd->type == CDK_DBTYPE_PK_KEYRING ||
           hd->type == CDK_DBTYPE_SK_KEYRING)
    {
      rc = cdk_stream_open (hd->name, &kr);

      if (rc)
        goto leave;
    }
  else
    {
      gnutls_assert ();
      return CDK_Inv_Mode;
    }

leave:

  *ret_kr = kr;
  return rc;
}


static int
find_by_keyid (cdk_kbnode_t knode, cdk_keydb_search_t ks)
{
  cdk_kbnode_t node;
  u32 keyid[2];

  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
          node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY ||
          node->pkt->pkttype == CDK_PKT_SECRET_KEY ||
          node->pkt->pkttype == CDK_PKT_SECRET_SUBKEY)
        {
          _cdk_pkt_get_keyid (node->pkt, keyid);
          switch (ks->type)
            {
            case CDK_DBSEARCH_SHORT_KEYID:
              if (keyid[1] == ks->u.keyid[1])
                return 1;
              break;

            case CDK_DBSEARCH_KEYID:
              if (KEYID_CMP (keyid, ks->u.keyid))
                return 1;
              break;

            default:
              _cdk_log_debug ("find_by_keyid: invalid mode = %d\n", ks->type);
              return 0;
            }
        }
    }
  return 0;
}


static int
find_by_fpr (cdk_kbnode_t knode, cdk_keydb_search_t ks)
{
  cdk_kbnode_t node;
  byte fpr[KEY_FPR_LEN];

  if (ks->type != CDK_DBSEARCH_FPR)
    return 0;

  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
          node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY ||
          node->pkt->pkttype == CDK_PKT_SECRET_KEY ||
          node->pkt->pkttype == CDK_PKT_SECRET_SUBKEY)
        {
          _cdk_pkt_get_fingerprint (node->pkt, fpr);
          if (!memcmp (ks->u.fpr, fpr, KEY_FPR_LEN))
            return 1;
          break;
        }
    }

  return 0;
}


static int
find_by_pattern (cdk_kbnode_t knode, cdk_keydb_search_t ks)
{
  cdk_kbnode_t node;
  size_t uidlen;
  char *name;

  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype != CDK_PKT_USER_ID)
        continue;
      if (node->pkt->pkt.user_id->attrib_img != NULL)
        continue;               /* Skip attribute packets. */
      uidlen = node->pkt->pkt.user_id->len;
      name = node->pkt->pkt.user_id->name;
      switch (ks->type)
        {
        case CDK_DBSEARCH_EXACT:
          if (name &&
              (strlen (ks->u.pattern) == uidlen &&
               !strncmp (ks->u.pattern, name, uidlen)))
            return 1;
          break;

        case CDK_DBSEARCH_SUBSTR:
          if (uidlen > 65536)
            break;
          if (name && strlen (ks->u.pattern) > uidlen)
            break;
          if (name && _cdk_memistr (name, uidlen, ks->u.pattern))
            return 1;
          break;

        default:               /* Invalid mode */
          return 0;
        }
    }
  return 0;
}


static void
keydb_cache_free (key_table_t cache)
{
  key_table_t c2;

  while (cache)
    {
      c2 = cache->next;
      cache->offset = 0;
      cdk_free (cache);
      cache = c2;
    }
}


static key_table_t
keydb_cache_find (cdk_keydb_search_t desc)
{
  key_table_t cache = desc->cache;
  key_table_t t;

  for (t = cache; t; t = t->next)
    {
      switch (desc->type)
        {
        case CDK_DBSEARCH_SHORT_KEYID:
        case CDK_DBSEARCH_KEYID:
          if (KEYID_CMP (desc->u.keyid, desc->u.keyid))
            return t;
          break;

        case CDK_DBSEARCH_EXACT:
          if (strlen (desc->u.pattern) == strlen (desc->u.pattern) &&
              !strcmp (desc->u.pattern, desc->u.pattern))
            return t;
          break;

        case CDK_DBSEARCH_SUBSTR:
          if (strstr (desc->u.pattern, desc->u.pattern))
            return t;
          break;

        case CDK_DBSEARCH_FPR:
          if (!memcmp (desc->u.fpr, desc->u.fpr, KEY_FPR_LEN))
            return t;
          break;
        }
    }

  return NULL;
}


static cdk_error_t
keydb_cache_add (cdk_keydb_search_t dbs, off_t offset)
{
  key_table_t k;

  if (dbs->ncache > KEYDB_CACHE_ENTRIES)
    return 0;                   /* FIXME: we should replace the last entry. */
  k = cdk_calloc (1, sizeof *k);
  if (!k)
    {
      gnutls_assert ();
      return CDK_Out_Of_Core;
    }

  k->offset = offset;

  k->next = dbs->cache;
  dbs->cache = k;
  dbs->ncache++;
  _cdk_log_debug ("cache: add entry off=%d type=%d\n", (int) offset,
                  (int) dbs->type);
  return 0;
}

static cdk_error_t
idx_init (cdk_keydb_hd_t db, cdk_keydb_search_t dbs)
{
  cdk_error_t ec, rc = 0;

  if (cdk_stream_get_length (db->fp) < 524288)
    {
      dbs->no_cache = 1;
      goto leave;
    }

  dbs->idx_name = keydb_idx_mkname (db->name);
  if (!dbs->idx_name)
    {
      rc = CDK_Out_Of_Core;
      goto leave;
    }
  ec = cdk_stream_open (dbs->idx_name, &dbs->idx);

  if (ec && !db->secret)
    {
      rc = keydb_idx_build (db->name);
      if (!rc)
        rc = cdk_stream_open (dbs->idx_name, &dbs->idx);
      if (!rc)
        {
          _cdk_log_debug ("create key index table\n");
        }
      else
        {
          /* This is no real error, it just means we can't create
             the index at the given directory. maybe we've no write
             access. in this case, we simply disable the index. */
          _cdk_log_debug ("disable key index table err=%d\n", rc);
          rc = 0;
          dbs->no_cache = 1;
        }
    }

leave:

  return rc;
}

/**
 * cdk_keydb_search_start:
 * @st: search handle
 * @db: key database handle
 * @type: specifies the search type
 * @desc: description which depends on the type
 *
 * Create a new keydb search object.
 **/
cdk_error_t
cdk_keydb_search_start (cdk_keydb_search_t * st, cdk_keydb_hd_t db, int type,
                        void *desc)
{
  u32 *keyid;
  char *p, tmp[3];
  int i;
  cdk_error_t rc;

  if (!db)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (type != CDK_DBSEARCH_NEXT && !desc)
    {
      gnutls_assert ();
      return CDK_Inv_Mode;
    }

  *st = cdk_calloc (1, sizeof (cdk_keydb_search_s));
  if (!(*st))
    {
      gnutls_assert ();
      return CDK_Out_Of_Core;
    }

  rc = idx_init (db, *st);
  if (rc != CDK_Success)
    {
      free (*st);
      gnutls_assert ();
      return rc;
    }

  (*st)->type = type;
  switch (type)
    {
    case CDK_DBSEARCH_EXACT:
    case CDK_DBSEARCH_SUBSTR:
      cdk_free ((*st)->u.pattern);
      (*st)->u.pattern = cdk_strdup (desc);
      if (!(*st)->u.pattern)
        {
          cdk_free (*st);
          gnutls_assert ();
          return CDK_Out_Of_Core;
        }
      break;

    case CDK_DBSEARCH_SHORT_KEYID:
      keyid = desc;
      (*st)->u.keyid[1] = keyid[0];
      break;

    case CDK_DBSEARCH_KEYID:
      keyid = desc;
      (*st)->u.keyid[0] = keyid[0];
      (*st)->u.keyid[1] = keyid[1];
      break;

    case CDK_DBSEARCH_FPR:
      memcpy ((*st)->u.fpr, desc, KEY_FPR_LEN);
      break;

    case CDK_DBSEARCH_NEXT:
      break;

    case CDK_DBSEARCH_AUTO:
      /* Override the type with the actual db search type. */
      (*st)->type = classify_data (desc, strlen (desc));
      switch ((*st)->type)
        {
        case CDK_DBSEARCH_SUBSTR:
        case CDK_DBSEARCH_EXACT:
          cdk_free ((*st)->u.pattern);
          p = (*st)->u.pattern = cdk_strdup (desc);
          if (!p)
            {
              cdk_free (*st);
              gnutls_assert ();
              return CDK_Out_Of_Core;
            }
          break;

        case CDK_DBSEARCH_SHORT_KEYID:
        case CDK_DBSEARCH_KEYID:
          p = desc;
          if (!strncmp (p, "0x", 2))
            p += 2;
          if (strlen (p) == 8)
            {
              (*st)->u.keyid[0] = 0;
              (*st)->u.keyid[1] = strtoul (p, NULL, 16);
            }
          else if (strlen (p) == 16)
            {
              (*st)->u.keyid[0] = strtoul (p, NULL, 16);
              (*st)->u.keyid[1] = strtoul (p + 8, NULL, 16);
            }
          else
            {                   /* Invalid key ID object. */
              cdk_free (*st);
              gnutls_assert ();
              return CDK_Inv_Mode;
            }
          break;

        case CDK_DBSEARCH_FPR:
          p = desc;
          if (strlen (p) != 2 * KEY_FPR_LEN)
            {
              cdk_free (*st);
              gnutls_assert ();
              return CDK_Inv_Mode;
            }
          for (i = 0; i < KEY_FPR_LEN; i++)
            {
              tmp[0] = p[2 * i];
              tmp[1] = p[2 * i + 1];
              tmp[2] = 0x00;
              (*st)->u.fpr[i] = strtoul (tmp, NULL, 16);
            }
          break;
        }
      break;

    default:
      cdk_free (*st);
      _cdk_log_debug ("cdk_keydb_search_start: invalid mode = %d\n", type);
      gnutls_assert ();
      return CDK_Inv_Mode;
    }

  return 0;
}


static cdk_error_t
keydb_pos_from_cache (cdk_keydb_hd_t hd, cdk_keydb_search_t ks,
                      int *r_cache_hit, off_t * r_off)
{
  key_table_t c;

  if (!hd || !r_cache_hit || !r_off)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  /* Reset the values. */
  *r_cache_hit = 0;
  *r_off = 0;

  c = keydb_cache_find (ks);
  if (c != NULL)
    {
      _cdk_log_debug ("cache: found entry in cache.\n");
      *r_cache_hit = 1;
      *r_off = c->offset;
      return 0;
    }

  /* No index cache available so we just return here. */
  if (!ks->idx)
    return 0;

  if (ks->idx)
    {
      if (ks->type == CDK_DBSEARCH_KEYID)
        {
          if (keydb_idx_search (ks->idx, ks->u.keyid, NULL, r_off))
            {
              gnutls_assert ();
              return CDK_Error_No_Key;
            }
          _cdk_log_debug ("cache: found keyid entry in idx table.\n");
          *r_cache_hit = 1;
        }
      else if (ks->type == CDK_DBSEARCH_FPR)
        {
          if (keydb_idx_search (ks->idx, NULL, ks->u.fpr, r_off))
            {
              gnutls_assert ();
              return CDK_Error_No_Key;
            }
          _cdk_log_debug ("cache: found fpr entry in idx table.\n");
          *r_cache_hit = 1;
        }
    }

  return 0;
}

void
cdk_keydb_search_release (cdk_keydb_search_t st)
{
  keydb_cache_free (st->cache);

  if (st->idx)
    cdk_stream_close (st->idx);

  if (!st)
    return;
  if (st->type == CDK_DBSEARCH_EXACT || st->type == CDK_DBSEARCH_SUBSTR)
    cdk_free (st->u.pattern);

  cdk_free (st);
}

/**
 * cdk_keydb_search:
 * @st: the search handle
 * @hd: the keydb object
 * @ret_key: kbnode object to store the key
 *
 * Search for a key in the given keyring. The search mode is handled
 * via @ks. If the key was found, @ret_key contains the key data.
 **/
cdk_error_t
cdk_keydb_search (cdk_keydb_search_t st, cdk_keydb_hd_t hd,
                  cdk_kbnode_t * ret_key)
{
  cdk_stream_t kr;
  cdk_kbnode_t knode;
  cdk_error_t rc = 0;
  off_t pos = 0, off = 0;
  int key_found = 0, cache_hit = 0;

  if (!hd || !ret_key || !st)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  *ret_key = NULL;
  kr = NULL;

  rc = _cdk_keydb_open (hd, &kr);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  if (!st->no_cache)
    {
      /* It is possible the index is not up-to-date and thus we do
         not find the requesed key. In this case, we reset cache hit
         and continue our normal search procedure. */
      rc = keydb_pos_from_cache (hd, st, &cache_hit, &off);
      if (rc)
        cache_hit = 0;
    }

  knode = NULL;

  while (!key_found && !rc)
    {
      if (cache_hit && st->type != CDK_DBSEARCH_NEXT)
        cdk_stream_seek (kr, off);
      else if (st->type == CDK_DBSEARCH_NEXT)
        cdk_stream_seek (kr, st->off);

      pos = cdk_stream_tell (kr);

      rc = cdk_keydb_get_keyblock (kr, &knode);

      if (rc)
        {
          if (rc == CDK_EOF)
            break;
          else
            {
              gnutls_assert ();
              return rc;
            }
        }

      switch (st->type)
        {
        case CDK_DBSEARCH_SHORT_KEYID:
        case CDK_DBSEARCH_KEYID:
          key_found = find_by_keyid (knode, st);
          break;

        case CDK_DBSEARCH_FPR:
          key_found = find_by_fpr (knode, st);
          break;

        case CDK_DBSEARCH_EXACT:
        case CDK_DBSEARCH_SUBSTR:
          key_found = find_by_pattern (knode, st);
          break;

        case CDK_DBSEARCH_NEXT:
          st->off = cdk_stream_tell (kr);
          key_found = knode ? 1 : 0;
          break;
        }

      if (key_found)
        {
          if (!keydb_cache_find (st))
            keydb_cache_add (st, pos);
          break;
        }

      cdk_kbnode_release (knode);
      knode = NULL;
    }

  if (key_found && rc == CDK_EOF)
    rc = 0;
  else if (rc == CDK_EOF && !key_found)
    {
      gnutls_assert ();
      rc = CDK_Error_No_Key;
    }
  *ret_key = key_found ? knode : NULL;
  return rc;
}

cdk_error_t
cdk_keydb_get_bykeyid (cdk_keydb_hd_t hd, u32 * keyid, cdk_kbnode_t * ret_key)
{
  cdk_error_t rc;
  cdk_keydb_search_t st;

  if (!hd || !keyid || !ret_key)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_KEYID, keyid);
  if (!rc)
    rc = cdk_keydb_search (st, hd, ret_key);

  cdk_keydb_search_release (st);
  return rc;
}


cdk_error_t
cdk_keydb_get_byfpr (cdk_keydb_hd_t hd, const byte * fpr,
                     cdk_kbnode_t * r_key)
{
  cdk_error_t rc;
  cdk_keydb_search_t st;

  if (!hd || !fpr || !r_key)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_FPR, (byte *) fpr);
  if (!rc)
    rc = cdk_keydb_search (st, hd, r_key);

  cdk_keydb_search_release (st);
  return rc;
}


cdk_error_t
cdk_keydb_get_bypattern (cdk_keydb_hd_t hd, const char *patt,
                         cdk_kbnode_t * ret_key)
{
  cdk_error_t rc;
  cdk_keydb_search_t st;

  if (!hd || !patt || !ret_key)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_SUBSTR, (char *) patt);
  if (!rc)
    rc = cdk_keydb_search (st, hd, ret_key);

  if (rc)
    gnutls_assert ();

  cdk_keydb_search_release (st);
  return rc;
}


static int
keydb_check_key (cdk_packet_t pkt)
{
  cdk_pkt_pubkey_t pk;
  int is_sk, valid;

  if (pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
      pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY)
    {
      pk = pkt->pkt.public_key;
      is_sk = 0;
    }
  else if (pkt->pkttype == CDK_PKT_SECRET_KEY ||
           pkt->pkttype == CDK_PKT_SECRET_SUBKEY)
    {
      pk = pkt->pkt.secret_key->pk;
      is_sk = 1;
    }
  else                          /* No key object. */
    return 0;
  valid = !pk->is_revoked && !pk->has_expired;
  if (is_sk)
    return valid;
  return valid && !pk->is_invalid;
}


/* Find the first kbnode with the requested packet type
   that represents a valid key. */
static cdk_kbnode_t
kbnode_find_valid (cdk_kbnode_t root, cdk_packet_type_t pkttype)
{
  cdk_kbnode_t n;

  for (n = root; n; n = n->next)
    {
      if (n->pkt->pkttype != pkttype)
        continue;
      if (keydb_check_key (n->pkt))
        return n;
    }

  return NULL;
}


static cdk_kbnode_t
keydb_find_byusage (cdk_kbnode_t root, int req_usage, int is_pk)
{
  cdk_kbnode_t node, key;
  int req_type;
  long timestamp;

  req_type = is_pk ? CDK_PKT_PUBLIC_KEY : CDK_PKT_SECRET_KEY;
  if (!req_usage)
    return kbnode_find_valid (root, req_type);

  node = cdk_kbnode_find (root, req_type);
  if (node && !keydb_check_key (node->pkt))
    return NULL;

  key = NULL;
  timestamp = 0;
  /* We iteratre over the all nodes and search for keys or
     subkeys which match the usage and which are not invalid.
     A timestamp is used to figure out the newest valid key. */
  for (node = root; node; node = node->next)
    {
      if (is_pk && (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
                    node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY)
          && keydb_check_key (node->pkt)
          && (node->pkt->pkt.public_key->pubkey_usage & req_usage))
        {
          if (node->pkt->pkt.public_key->timestamp > timestamp)
            key = node;
        }
      if (!is_pk && (node->pkt->pkttype == CDK_PKT_SECRET_KEY ||
                     node->pkt->pkttype == CDK_PKT_SECRET_SUBKEY)
          && keydb_check_key (node->pkt)
          && (node->pkt->pkt.secret_key->pk->pubkey_usage & req_usage))
        {
          if (node->pkt->pkt.secret_key->pk->timestamp > timestamp)
            key = node;
        }

    }
  return key;
}


static cdk_kbnode_t
keydb_find_bykeyid (cdk_kbnode_t root, const u32 * keyid, int search_mode)
{
  cdk_kbnode_t node;
  u32 kid[2];

  for (node = root; node; node = node->next)
    {
      if (!_cdk_pkt_get_keyid (node->pkt, kid))
        continue;
      if (search_mode == CDK_DBSEARCH_SHORT_KEYID && kid[1] == keyid[1])
        return node;
      else if (kid[0] == keyid[0] && kid[1] == keyid[1])
        return node;
    }
  return NULL;
}


cdk_error_t
_cdk_keydb_get_sk_byusage (cdk_keydb_hd_t hd, const char *name,
                           cdk_seckey_t * ret_sk, int usage)
{
  cdk_kbnode_t knode = NULL;
  cdk_kbnode_t node, sk_node, pk_node;
  cdk_pkt_seckey_t sk;
  cdk_error_t rc;
  const char *s;
  int pkttype;
  cdk_keydb_search_t st;

  if (!ret_sk || !usage)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  if (!hd)
    {
      gnutls_assert ();
      return CDK_Error_No_Keyring;
    }

  *ret_sk = NULL;
  rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_AUTO, (char *) name);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  rc = cdk_keydb_search (st, hd, &knode);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  cdk_keydb_search_release (st);

  sk_node = keydb_find_byusage (knode, usage, 0);
  if (!sk_node)
    {
      cdk_kbnode_release (knode);
      gnutls_assert ();
      return CDK_Unusable_Key;
    }

  /* We clone the node with the secret key to avoid that the
     packet will be released. */
  _cdk_kbnode_clone (sk_node);
  sk = sk_node->pkt->pkt.secret_key;

  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype == CDK_PKT_USER_ID)
        {
          s = node->pkt->pkt.user_id->name;
          if (sk && !sk->pk->uid && _cdk_memistr (s, strlen (s), name))
            {
              _cdk_copy_userid (&sk->pk->uid, node->pkt->pkt.user_id);
              break;
            }
        }
    }

  /* To find the self signature, we need the primary public key because
     the selected secret key might be different from the primary key. */
  pk_node = cdk_kbnode_find (knode, CDK_PKT_SECRET_KEY);
  if (!pk_node)
    {
      cdk_kbnode_release (knode);
      gnutls_assert ();
      return CDK_Unusable_Key;
    }
  node = find_selfsig_node (knode, pk_node->pkt->pkt.secret_key->pk);
  if (sk->pk->uid && node)
    _cdk_copy_signature (&sk->pk->uid->selfsig, node->pkt->pkt.signature);

  /* We only release the outer packet. */
  _cdk_pkt_detach_free (sk_node->pkt, &pkttype, (void *) &sk);
  cdk_kbnode_release (knode);
  *ret_sk = sk;
  return rc;
}


cdk_error_t
_cdk_keydb_get_pk_byusage (cdk_keydb_hd_t hd, const char *name,
                           cdk_pubkey_t * ret_pk, int usage)
{
  cdk_kbnode_t knode, node, pk_node;
  cdk_pkt_pubkey_t pk;
  const char *s;
  cdk_error_t rc;
  cdk_keydb_search_t st;

  if (!ret_pk || !usage)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (!hd)
    {
      gnutls_assert ();
      return CDK_Error_No_Keyring;
    }

  *ret_pk = NULL;
  rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_AUTO, (char *) name);
  if (!rc)
    rc = cdk_keydb_search (st, hd, &knode);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  cdk_keydb_search_release (st);

  node = keydb_find_byusage (knode, usage, 1);
  if (!node)
    {
      cdk_kbnode_release (knode);
      gnutls_assert ();
      return CDK_Unusable_Key;
    }

  pk = NULL;
  _cdk_copy_pubkey (&pk, node->pkt->pkt.public_key);
  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype == CDK_PKT_USER_ID)
        {
          s = node->pkt->pkt.user_id->name;
          if (pk && !pk->uid && _cdk_memistr (s, strlen (s), name))
            {
              _cdk_copy_userid (&pk->uid, node->pkt->pkt.user_id);
              break;
            }
        }
    }

  /* Same as in the sk code, the selected key can be a sub key 
     and thus we need the primary key to find the self sig. */
  pk_node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY);
  if (!pk_node)
    {
      cdk_kbnode_release (knode);
      gnutls_assert ();
      return CDK_Unusable_Key;
    }
  node = find_selfsig_node (knode, pk_node->pkt->pkt.public_key);
  if (pk->uid && node)
    _cdk_copy_signature (&pk->uid->selfsig, node->pkt->pkt.signature);
  cdk_kbnode_release (knode);

  *ret_pk = pk;
  return rc;
}


/**
 * cdk_keydb_get_pk:
 * @hd: key db handle
 * @keyid: keyid of the key
 * @r_pk: the allocated public key
 * 
 * Perform a key database search by keyid and return the raw public
 * key without any signatures or user id's.
 **/
cdk_error_t
cdk_keydb_get_pk (cdk_keydb_hd_t hd, u32 * keyid, cdk_pubkey_t * r_pk)
{
  cdk_kbnode_t knode = NULL, node;
  cdk_pubkey_t pk;
  cdk_error_t rc;
  size_t s_type;
  int pkttype;
  cdk_keydb_search_t st;

  if (!keyid || !r_pk)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (!hd)
    {
      gnutls_assert ();
      return CDK_Error_No_Keyring;
    }

  *r_pk = NULL;
  s_type = !keyid[0] ? CDK_DBSEARCH_SHORT_KEYID : CDK_DBSEARCH_KEYID;
  rc = cdk_keydb_search_start (&st, hd, s_type, keyid);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }
  rc = cdk_keydb_search (st, hd, &knode);
  cdk_keydb_search_release (st);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  node = keydb_find_bykeyid (knode, keyid, s_type);
  if (!node)
    {
      cdk_kbnode_release (knode);
      gnutls_assert ();
      return CDK_Error_No_Key;
    }

  /* See comment in cdk_keydb_get_sk() */
  _cdk_pkt_detach_free (node->pkt, &pkttype, (void *) &pk);
  *r_pk = pk;
  _cdk_kbnode_clone (node);
  cdk_kbnode_release (knode);

  return rc;
}


/**
 * cdk_keydb_get_sk:
 * @hd: key db handle
 * @keyid: the keyid of the key
 * @ret_sk: the allocated secret key
 * 
 * Perform a key database search by keyid and return
 * only the raw secret key without the additional nodes,
 * like the user id or the signatures.
 **/
cdk_error_t
cdk_keydb_get_sk (cdk_keydb_hd_t hd, u32 * keyid, cdk_seckey_t * ret_sk)
{
  cdk_kbnode_t snode, node;
  cdk_seckey_t sk;
  cdk_error_t rc;
  int pkttype;

  if (!keyid || !ret_sk)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (!hd)
    {
      gnutls_assert ();
      return CDK_Error_No_Keyring;
    }

  *ret_sk = NULL;
  rc = cdk_keydb_get_bykeyid (hd, keyid, &snode);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  node = keydb_find_bykeyid (snode, keyid, CDK_DBSEARCH_KEYID);
  if (!node)
    {
      cdk_kbnode_release (snode);
      gnutls_assert ();
      return CDK_Error_No_Key;
    }

  /* We need to release the packet itself but not its contents
     and thus we detach the openpgp packet and release the structure. */
  _cdk_pkt_detach_free (node->pkt, &pkttype, (void *) &sk);
  _cdk_kbnode_clone (node);
  cdk_kbnode_release (snode);

  *ret_sk = sk;
  return 0;
}


static int
is_selfsig (cdk_kbnode_t node, const u32 * keyid)
{
  cdk_pkt_signature_t sig;

  if (node->pkt->pkttype != CDK_PKT_SIGNATURE)
    return 0;
  sig = node->pkt->pkt.signature;
  if ((sig->sig_class >= 0x10 && sig->sig_class <= 0x13) &&
      sig->keyid[0] == keyid[0] && sig->keyid[1] == keyid[1])
    return 1;

  return 0;
}


/* Find the newest self signature for the public key @pk
   and return the signature node. */
static cdk_kbnode_t
find_selfsig_node (cdk_kbnode_t key, cdk_pkt_pubkey_t pk)
{
  cdk_kbnode_t n, sig;
  unsigned int ts;
  u32 keyid[2];

  cdk_pk_get_keyid (pk, keyid);
  sig = NULL;
  ts = 0;
  for (n = key; n; n = n->next)
    {
      if (is_selfsig (n, keyid) && n->pkt->pkt.signature->timestamp > ts)
        {
          ts = n->pkt->pkt.signature->timestamp;
          sig = n;
        }
    }

  return sig;
}

static unsigned int
key_usage_to_cdk_usage (unsigned int usage)
{
  unsigned key_usage = 0;

  if (usage & 0x01)             /* cert + sign data */
    key_usage |= CDK_KEY_USG_CERT_SIGN;
  if (usage & 0x02)             /* cert + sign data */
    key_usage |= CDK_KEY_USG_DATA_SIGN;
  if (usage & 0x04)             /* encrypt comm. + storage */
    key_usage |= CDK_KEY_USG_COMM_ENCR;
  if (usage & 0x08)             /* encrypt comm. + storage */
    key_usage |= CDK_KEY_USG_STORAGE_ENCR;
  if (usage & 0x10)             /* encrypt comm. + storage */
    key_usage |= CDK_KEY_USG_SPLIT_KEY;
  if (usage & 0x20)
    key_usage |= CDK_KEY_USG_AUTH;
  if (usage & 0x80)             /* encrypt comm. + storage */
    key_usage |= CDK_KEY_USG_SHARED_KEY;

  return key_usage;
}

static cdk_error_t
keydb_merge_selfsig (cdk_kbnode_t key, u32 * keyid)
{
  cdk_kbnode_t node, kbnode, unode;
  cdk_subpkt_t s = NULL;
  cdk_pkt_signature_t sig = NULL;
  cdk_pkt_userid_t uid = NULL;
  const byte *symalg = NULL, *hashalg = NULL, *compalg = NULL;
  size_t nsymalg = 0, nhashalg = 0, ncompalg = 0, n = 0;
  size_t key_expire = 0;

  if (!key)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  for (node = key; node; node = node->next)
    {
      if (!is_selfsig (node, keyid))
        continue;
      unode = cdk_kbnode_find_prev (key, node, CDK_PKT_USER_ID);
      if (!unode)
        {
          gnutls_assert ();
          return CDK_Error_No_Key;
        }
      uid = unode->pkt->pkt.user_id;
      sig = node->pkt->pkt.signature;
      s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PRIMARY_UID);
      if (s)
        uid->is_primary = 1;
      s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_FEATURES);
      if (s && s->size == 1 && s->d[0] & 0x01)
        uid->mdc_feature = 1;
      s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_KEY_EXPIRE);
      if (s && s->size == 4)
        key_expire = _cdk_buftou32 (s->d);
      s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PREFS_SYM);
      if (s)
        {
          symalg = s->d;
          nsymalg = s->size;
          n += s->size + 1;
        }
      s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PREFS_HASH);
      if (s)
        {
          hashalg = s->d;
          nhashalg = s->size;
          n += s->size + 1;
        }
      s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PREFS_ZIP);
      if (s)
        {
          compalg = s->d;
          ncompalg = s->size;
          n += s->size + 1;
        }
      if (uid->prefs != NULL)
        cdk_free (uid->prefs);
      if (!n || !hashalg || !compalg || !symalg)
        uid->prefs = NULL;
      else
        {
          uid->prefs = cdk_calloc (1, sizeof (*uid->prefs) * (n + 1));
          if (!uid->prefs)
            {
              gnutls_assert ();
              return CDK_Out_Of_Core;
            }
          n = 0;
          for (; nsymalg; nsymalg--, n++)
            {
              uid->prefs[n].type = CDK_PREFTYPE_SYM;
              uid->prefs[n].value = *symalg++;
            }
          for (; nhashalg; nhashalg--, n++)
            {
              uid->prefs[n].type = CDK_PREFTYPE_HASH;
              uid->prefs[n].value = *hashalg++;
            }
          for (; ncompalg; ncompalg--, n++)
            {
              uid->prefs[n].type = CDK_PREFTYPE_ZIP;
              uid->prefs[n].value = *compalg++;
            }

          uid->prefs[n].type = CDK_PREFTYPE_NONE;       /* end of list marker */
          uid->prefs[n].value = 0;
          uid->prefs_size = n;
        }
    }

  /* Now we add the extracted information to the primary key. */
  kbnode = cdk_kbnode_find (key, CDK_PKT_PUBLIC_KEY);
  if (kbnode)
    {
      cdk_pkt_pubkey_t pk = kbnode->pkt->pkt.public_key;
      if (uid && uid->prefs && n)
        {
          if (pk->prefs != NULL)
            cdk_free (pk->prefs);
          pk->prefs = _cdk_copy_prefs (uid->prefs);
          pk->prefs_size = n;
        }
      if (key_expire)
        {
          pk->expiredate = pk->timestamp + key_expire;
          pk->has_expired = pk->expiredate > (u32) gnutls_time (NULL) ? 0 : 1;
        }

      pk->is_invalid = 0;
    }

  return 0;
}


static cdk_error_t
keydb_parse_allsigs (cdk_kbnode_t knode, cdk_keydb_hd_t hd, int check)
{
  cdk_kbnode_t node, kb;
  cdk_pkt_signature_t sig;
  cdk_pkt_pubkey_t pk;
  cdk_subpkt_t s = NULL;
  u32 expiredate = 0, curtime = (u32) gnutls_time (NULL);
  u32 keyid[2];

  if (!knode)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (check && !hd)
    {
      gnutls_assert ();
      return CDK_Inv_Mode;
    }

  kb = cdk_kbnode_find (knode, CDK_PKT_SECRET_KEY);
  if (kb)
    return 0;

  /* Reset */
  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype == CDK_PKT_USER_ID)
        node->pkt->pkt.user_id->is_revoked = 0;
      else if (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
               node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY)
        node->pkt->pkt.public_key->is_revoked = 0;
    }

  kb = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY);
  if (!kb)
    {
      gnutls_assert ();
      return CDK_Wrong_Format;
    }
  cdk_pk_get_keyid (kb->pkt->pkt.public_key, keyid);

  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype == CDK_PKT_SIGNATURE)
        {
          sig = node->pkt->pkt.signature;
          /* Revocation certificates for primary keys */
          if (sig->sig_class == 0x20)
            {
              kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_KEY);
              if (kb)
                {
                  kb->pkt->pkt.public_key->is_revoked = 1;
                  if (check)
                    _cdk_pk_check_sig (hd, kb, node, NULL, NULL);
                }
              else
                {
                  gnutls_assert ();
                  return CDK_Error_No_Key;
                }
            }
          /* Revocation certificates for subkeys */
          else if (sig->sig_class == 0x28)
            {
              kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_SUBKEY);
              if (kb)
                {
                  kb->pkt->pkt.public_key->is_revoked = 1;
                  if (check)
                    _cdk_pk_check_sig (hd, kb, node, NULL, NULL);
                }
              else
                {
                  gnutls_assert ();
                  return CDK_Error_No_Key;
                }
            }
          /* Revocation certifcates for user ID's */
          else if (sig->sig_class == 0x30)
            {
              if (sig->keyid[0] != keyid[0] || sig->keyid[1] != keyid[1])
                continue;       /* revokes an earlier signature, no userID. */
              kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_USER_ID);
              if (kb)
                {
                  kb->pkt->pkt.user_id->is_revoked = 1;
                  if (check)
                    _cdk_pk_check_sig (hd, kb, node, NULL, NULL);
                }
              else
                {
                  gnutls_assert ();
                  return CDK_Error_No_Key;
                }
            }
          /* Direct certificates for primary keys */
          else if (sig->sig_class == 0x1F)
            {
              kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_KEY);
              if (kb)
                {
                  pk = kb->pkt->pkt.public_key;
                  pk->is_invalid = 0;
                  s = cdk_subpkt_find (node->pkt->pkt.signature->hashed,
                                       CDK_SIGSUBPKT_KEY_EXPIRE);
                  if (s)
                    {
                      expiredate = _cdk_buftou32 (s->d);
                      pk->expiredate = pk->timestamp + expiredate;
                      pk->has_expired = pk->expiredate > curtime ? 0 : 1;
                    }
                  if (check)
                    _cdk_pk_check_sig (hd, kb, node, NULL, NULL);
                }
              else
                {
                  gnutls_assert ();
                  return CDK_Error_No_Key;
                }
            }
          /* Direct certificates for subkeys */
          else if (sig->sig_class == 0x18)
            {
              kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_SUBKEY);
              if (kb)
                {
                  pk = kb->pkt->pkt.public_key;
                  pk->is_invalid = 0;
                  s = cdk_subpkt_find (node->pkt->pkt.signature->hashed,
                                       CDK_SIGSUBPKT_KEY_EXPIRE);
                  if (s)
                    {
                      expiredate = _cdk_buftou32 (s->d);
                      pk->expiredate = pk->timestamp + expiredate;
                      pk->has_expired = pk->expiredate > curtime ? 0 : 1;
                    }
                  if (check)
                    _cdk_pk_check_sig (hd, kb, node, NULL, NULL);
                }
              else
                {
                  gnutls_assert ();
                  return CDK_Error_No_Key;
                }
            }
        }
    }
  node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY);
  if (node && node->pkt->pkt.public_key->version == 3)
    {
      /* v3 public keys have no additonal signatures for the key directly.
         we say the key is valid when we have at least a self signature. */
      pk = node->pkt->pkt.public_key;
      for (node = knode; node; node = node->next)
        {
          if (is_selfsig (node, keyid))
            {
              pk->is_invalid = 0;
              break;
            }
        }
    }
  if (node && (node->pkt->pkt.public_key->is_revoked ||
               node->pkt->pkt.public_key->has_expired))
    {
      /* If the primary key has been revoked, mark all subkeys as invalid
         because without a primary key they are not useable */
      for (node = knode; node; node = node->next)
        {
          if (node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY)
            node->pkt->pkt.public_key->is_invalid = 1;
        }
    }

  return 0;
}

static void
add_key_usage (cdk_kbnode_t knode, u32 keyid[2], unsigned int usage)
{
  cdk_kbnode_t p, ctx;
  cdk_packet_t pkt;

  ctx = NULL;
  while ((p = cdk_kbnode_walk (knode, &ctx, 0)))
    {
      pkt = cdk_kbnode_get_packet (p);
      if ((pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY
           || pkt->pkttype == CDK_PKT_PUBLIC_KEY)
          && pkt->pkt.public_key->keyid[0] == keyid[0]
          && pkt->pkt.public_key->keyid[1] == keyid[1])
        {
          pkt->pkt.public_key->pubkey_usage = usage;
          return;
        }
    }
  return;
}

cdk_error_t
cdk_keydb_get_keyblock (cdk_stream_t inp, cdk_kbnode_t * r_knode)
{
  cdk_packet_t pkt;
  cdk_kbnode_t knode, node;
  cdk_desig_revoker_t revkeys;
  cdk_error_t rc;
  u32 keyid[2], main_keyid[2];
  off_t old_off;
  int key_seen, got_key;

  if (!inp || !r_knode)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  /* Reset all values. */
  keyid[0] = keyid[1] = 0;
  main_keyid[0] = main_keyid[1] = 0;
  revkeys = NULL;
  knode = NULL;
  key_seen = got_key = 0;

  *r_knode = NULL;
  rc = CDK_EOF;
  while (!cdk_stream_eof (inp))
    {
      cdk_pkt_new (&pkt);
      old_off = cdk_stream_tell (inp);
      rc = cdk_pkt_read (inp, pkt);
      if (rc)
        {
          cdk_pkt_release (pkt);
          if (rc == CDK_EOF)
            break;
          else
            {                   /* Release all packets we reached so far. */
              _cdk_log_debug ("keydb_get_keyblock: error %d\n", rc);
              cdk_kbnode_release (knode);
              gnutls_assert ();
              return rc;
            }
        }

      if (pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
          pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY ||
          pkt->pkttype == CDK_PKT_SECRET_KEY ||
          pkt->pkttype == CDK_PKT_SECRET_SUBKEY)
        {
          if (key_seen && (pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
                           pkt->pkttype == CDK_PKT_SECRET_KEY))
            {
              /* The next key starts here so set the file pointer
                 and leave the loop. */
              cdk_stream_seek (inp, old_off);
              cdk_pkt_release (pkt);
              break;
            }
          if (pkt->pkttype == CDK_PKT_PUBLIC_KEY ||
              pkt->pkttype == CDK_PKT_SECRET_KEY)
            {
              _cdk_pkt_get_keyid (pkt, main_keyid);
              key_seen = 1;
            }
          else if (pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY ||
                   pkt->pkttype == CDK_PKT_SECRET_SUBKEY)
            {
              if (pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY)
                {
                  pkt->pkt.public_key->main_keyid[0] = main_keyid[0];
                  pkt->pkt.public_key->main_keyid[1] = main_keyid[1];
                }
              else
                {
                  pkt->pkt.secret_key->main_keyid[0] = main_keyid[0];
                  pkt->pkt.secret_key->main_keyid[1] = main_keyid[1];
                }
            }
          /* We save this for the signature */
          _cdk_pkt_get_keyid (pkt, keyid);
          got_key = 1;
        }
      else if (pkt->pkttype == CDK_PKT_USER_ID)
        ;
      else if (pkt->pkttype == CDK_PKT_SIGNATURE)
        {
          cdk_subpkt_t s;

          pkt->pkt.signature->key[0] = keyid[0];
          pkt->pkt.signature->key[1] = keyid[1];
          if (pkt->pkt.signature->sig_class == 0x1F &&
              pkt->pkt.signature->revkeys)
            revkeys = pkt->pkt.signature->revkeys;

          s =
            cdk_subpkt_find (pkt->pkt.signature->hashed,
                             CDK_SIGSUBPKT_KEY_FLAGS);
          if (s)
            {
              unsigned int key_usage = key_usage_to_cdk_usage (s->d[0]);
              add_key_usage (knode, pkt->pkt.signature->key, key_usage);
            }
        }
      node = cdk_kbnode_new (pkt);
      if (!knode)
        knode = node;
      else
        _cdk_kbnode_add (knode, node);
    }

  if (got_key)
    {
      keydb_merge_selfsig (knode, main_keyid);
      rc = keydb_parse_allsigs (knode, NULL, 0);
      if (revkeys)
        {
          node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY);
          if (node)
            node->pkt->pkt.public_key->revkeys = revkeys;
        }
    }
  else
    cdk_kbnode_release (knode);
  *r_knode = got_key ? knode : NULL;

  /* It is possible that we are in an EOF condition after we
     successfully read a keyblock. For example if the requested
     key is the last in the file. */
  if (rc == CDK_EOF && got_key)
    rc = 0;
  return rc;
}


/* Return the type of the given data. In case it cannot be classified,
   a substring search will be performed. */
static int
classify_data (const byte * buf, size_t len)
{
  int type;
  unsigned int i;

  if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X'))
    {                           /* Skip hex prefix. */
      buf += 2;
      len -= 2;
    }

  /* The length of the data does not match either a keyid or a fingerprint. */
  if (len != 8 && len != 16 && len != 40)
    return CDK_DBSEARCH_SUBSTR;

  for (i = 0; i < len; i++)
    {
      if (!isxdigit (buf[i]))
        return CDK_DBSEARCH_SUBSTR;
    }
  if (i != len)
    return CDK_DBSEARCH_SUBSTR;
  switch (len)
    {
    case 8:
      type = CDK_DBSEARCH_SHORT_KEYID;
      break;
    case 16:
      type = CDK_DBSEARCH_KEYID;
      break;
    case 40:
      type = CDK_DBSEARCH_FPR;
      break;
    default:
      type = CDK_DBSEARCH_SUBSTR;
      break;
    }

  return type;
}


/**
 * cdk_keydb_export:
 * @hd: the keydb handle
 * @out: the output stream
 * @remusr: the list of key pattern to export
 *
 * Export a list of keys to the given output stream.
 * Use string list with names for pattering searching.
 * This procedure strips local signatures.
 **/
cdk_error_t
cdk_keydb_export (cdk_keydb_hd_t hd, cdk_stream_t out, cdk_strlist_t remusr)
{
  cdk_kbnode_t knode, node;
  cdk_strlist_t r;
  cdk_error_t rc;
  int old_ctb;
  cdk_keydb_search_t st;

  for (r = remusr; r; r = r->next)
    {
      rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_AUTO, r->d);
      if (rc)
        {
          gnutls_assert ();
          return rc;
        }
      rc = cdk_keydb_search (st, hd, &knode);
      cdk_keydb_search_release (st);

      if (rc)
        {
          gnutls_assert ();
          return rc;
        }

      node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY);
      if (!node)
        {
          gnutls_assert ();
          return CDK_Error_No_Key;
        }

      /* If the key is a version 3 key, use the old packet
         format for the output. */
      if (node->pkt->pkt.public_key->version == 3)
        old_ctb = 1;
      else
        old_ctb = 0;

      for (node = knode; node; node = node->next)
        {
          /* No specified format; skip them */
          if (node->pkt->pkttype == CDK_PKT_RING_TRUST)
            continue;
          /* We never export local signed signatures */
          if (node->pkt->pkttype == CDK_PKT_SIGNATURE &&
              !node->pkt->pkt.signature->flags.exportable)
            continue;
          /* Filter out invalid signatures */
          if (node->pkt->pkttype == CDK_PKT_SIGNATURE &&
              (!KEY_CAN_SIGN (node->pkt->pkt.signature->pubkey_algo)))
            continue;

          /* Adjust the ctb flag if needed. */
          node->pkt->old_ctb = old_ctb;
          rc = cdk_pkt_write (out, node->pkt);
          if (rc)
            {
              cdk_kbnode_release (knode);
              gnutls_assert ();
              return rc;
            }
        }
      cdk_kbnode_release (knode);
      knode = NULL;
    }
  return 0;
}


static cdk_packet_t
find_key_packet (cdk_kbnode_t knode, int *r_is_sk)
{
  cdk_packet_t pkt;

  pkt = cdk_kbnode_find_packet (knode, CDK_PKT_PUBLIC_KEY);
  if (!pkt)
    {
      pkt = cdk_kbnode_find_packet (knode, CDK_PKT_SECRET_KEY);
      if (r_is_sk)
        *r_is_sk = pkt ? 1 : 0;
    }
  return pkt;
}


/* Return 1 if the is allowd in a key node. */
static int
is_key_node (cdk_kbnode_t node)
{
  switch (node->pkt->pkttype)
    {
    case CDK_PKT_SIGNATURE:
    case CDK_PKT_SECRET_KEY:
    case CDK_PKT_PUBLIC_KEY:
    case CDK_PKT_SECRET_SUBKEY:
    case CDK_PKT_PUBLIC_SUBKEY:
    case CDK_PKT_USER_ID:
    case CDK_PKT_ATTRIBUTE:
      return 1;

    default:
      return 0;
    }

  return 0;
}


cdk_error_t
cdk_keydb_import (cdk_keydb_hd_t hd, cdk_kbnode_t knode)
{
  cdk_kbnode_t node, chk;
  cdk_packet_t pkt;
  cdk_stream_t out;
  cdk_error_t rc;
  u32 keyid[2];

  if (!hd || !knode)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  pkt = find_key_packet (knode, NULL);
  if (!pkt)
    {
      gnutls_assert ();
      return CDK_Inv_Packet;
    }

  _cdk_pkt_get_keyid (pkt, keyid);
  chk = NULL;
  cdk_keydb_get_bykeyid (hd, keyid, &chk);
  if (chk)
    {                           /* FIXME: search for new signatures */
      cdk_kbnode_release (chk);
      return 0;
    }

  /* We append data to the stream so we need to close
     the stream here to re-open it later. */
  if (hd->fp)
    {
      cdk_stream_close (hd->fp);
      hd->fp = NULL;
    }

  rc = _cdk_stream_append (hd->name, &out);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  for (node = knode; node; node = node->next)
    {
      if (node->pkt->pkttype == CDK_PKT_RING_TRUST)
        continue;               /* No uniformed syntax for this packet */
      if (node->pkt->pkttype == CDK_PKT_SIGNATURE &&
          !node->pkt->pkt.signature->flags.exportable)
        {
          _cdk_log_debug ("key db import: skip local signature\n");
          continue;
        }

      if (!is_key_node (node))
        {
          _cdk_log_debug ("key db import: skip invalid node of type %d\n",
                          node->pkt->pkttype);
          continue;
        }

      rc = cdk_pkt_write (out, node->pkt);
      if (rc)
        {
          cdk_stream_close (out);
          gnutls_assert ();
          return rc;
        }
    }

  cdk_stream_close (out);
  hd->stats.new_keys++;

  return 0;
}


cdk_error_t
_cdk_keydb_check_userid (cdk_keydb_hd_t hd, u32 * keyid, const char *id)
{
  cdk_kbnode_t knode = NULL, unode = NULL;
  cdk_error_t rc;
  int check;
  cdk_keydb_search_t st;

  if (!hd)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }

  rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_KEYID, keyid);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }
  rc = cdk_keydb_search (st, hd, &knode);
  cdk_keydb_search_release (st);

  if (rc)
    {
      gnutls_assert ();
      return rc;
    }

  rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_EXACT, (char *) id);
  if (!rc)
    {
      rc = cdk_keydb_search (st, hd, &unode);
      cdk_keydb_search_release (st);
    }
  if (rc)
    {
      cdk_kbnode_release (knode);
      gnutls_assert ();
      return rc;
    }

  check = 0;
  cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_KEYID, keyid);
  if (unode && find_by_keyid (unode, st))
    check++;
  cdk_keydb_search_release (st);
  cdk_kbnode_release (unode);

  cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_EXACT, (char *) id);
  if (knode && find_by_pattern (knode, st))
    check++;
  cdk_keydb_search_release (st);
  cdk_kbnode_release (knode);

  return check == 2 ? 0 : CDK_Inv_Value;
}


/**
 * cdk_keydb_check_sk:
 * @hd: the key db handle
 * @keyid: the 64-bit keyid
 * 
 * Check if a secret key with the given key ID is available
 * in the key database.
 **/
cdk_error_t
cdk_keydb_check_sk (cdk_keydb_hd_t hd, u32 * keyid)
{
  cdk_stream_t db;
  cdk_packet_t pkt;
  cdk_error_t rc;
  u32 kid[2];

  if (!hd || !keyid)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (!hd->secret)
    {
      gnutls_assert ();
      return CDK_Inv_Mode;
    }

  rc = _cdk_keydb_open (hd, &db);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }
  cdk_pkt_new (&pkt);
  while (!cdk_pkt_read (db, pkt))
    {
      if (pkt->pkttype != CDK_PKT_SECRET_KEY &&
          pkt->pkttype != CDK_PKT_SECRET_SUBKEY)
        {
          cdk_pkt_free (pkt);
          continue;
        }
      cdk_sk_get_keyid (pkt->pkt.secret_key, kid);
      if (KEYID_CMP (kid, keyid))
        {
          cdk_pkt_release (pkt);
          return 0;
        }
      cdk_pkt_free (pkt);
    }
  cdk_pkt_release (pkt);
  gnutls_assert ();
  return CDK_Error_No_Key;
}


/**
 * cdk_listkey_start:
 * @r_ctx: pointer to store the new context
 * @db: the key database handle
 * @patt: string pattern
 * @fpatt: recipients from a stringlist to show
 *
 * Prepare a key listing with the given parameters. Two modes are supported.
 * The first mode uses string pattern to determine if the key should be
 * returned or not. The other mode uses a string list to request the key
 * which should be listed.
 **/
cdk_error_t
cdk_listkey_start (cdk_listkey_t * r_ctx, cdk_keydb_hd_t db,
                   const char *patt, cdk_strlist_t fpatt)
{
  cdk_listkey_t ctx;
  cdk_stream_t inp;
  cdk_error_t rc;

  if (!r_ctx || !db)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if ((patt && fpatt) || (!patt && !fpatt))
    {
      gnutls_assert ();
      return CDK_Inv_Mode;
    }
  rc = _cdk_keydb_open (db, &inp);
  if (rc)
    {
      gnutls_assert ();
      return rc;
    }
  ctx = cdk_calloc (1, sizeof *ctx);
  if (!ctx)
    {
      gnutls_assert ();
      return CDK_Out_Of_Core;
    }
  ctx->db = db;
  ctx->inp = inp;
  if (patt)
    {
      ctx->u.patt = cdk_strdup (patt);
      if (!ctx->u.patt)
        {
          gnutls_assert ();
          return CDK_Out_Of_Core;
        }
    }
  else if (fpatt)
    {
      cdk_strlist_t l;
      for (l = fpatt; l; l = l->next)
        cdk_strlist_add (&ctx->u.fpatt, l->d);
    }
  ctx->type = patt ? 1 : 0;
  ctx->init = 1;
  *r_ctx = ctx;
  return 0;
}


/**
 * cdk_listkey_close:
 * @ctx: the list key context
 *
 * Free the list key context.
 **/
void
cdk_listkey_close (cdk_listkey_t ctx)
{
  if (!ctx)
    return;

  if (ctx->type)
    cdk_free (ctx->u.patt);
  else
    cdk_strlist_free (ctx->u.fpatt);
  cdk_free (ctx);
}


/**
 * cdk_listkey_next:
 * @ctx: list key context
 * @r_key: the pointer to the new key node object
 *
 * Retrieve the next key from the pattern of the key list context.
 **/
cdk_error_t
cdk_listkey_next (cdk_listkey_t ctx, cdk_kbnode_t * ret_key)
{
  if (!ctx || !ret_key)
    {
      gnutls_assert ();
      return CDK_Inv_Value;
    }
  if (!ctx->init)
    {
      gnutls_assert ();
      return CDK_Inv_Mode;
    }

  if (ctx->type && ctx->u.patt[0] == '*')
    return cdk_keydb_get_keyblock (ctx->inp, ret_key);
  else if (ctx->type)
    {
      cdk_kbnode_t node;
      struct cdk_keydb_search_s ks;
      cdk_error_t rc;

      for (;;)
        {
          rc = cdk_keydb_get_keyblock (ctx->inp, &node);
          if (rc)
            {
              gnutls_assert ();
              return rc;
            }
          memset (&ks, 0, sizeof (ks));
          ks.type = CDK_DBSEARCH_SUBSTR;
          ks.u.pattern = ctx->u.patt;
          if (find_by_pattern (node, &ks))
            {
              *ret_key = node;
              return 0;
            }
          cdk_kbnode_release (node);
          node = NULL;
        }
    }
  else
    {
      if (!ctx->t)
        ctx->t = ctx->u.fpatt;
      else if (ctx->t->next)
        ctx->t = ctx->t->next;
      else
        return CDK_EOF;
      return cdk_keydb_get_bypattern (ctx->db, ctx->t->d, ret_key);
    }
  gnutls_assert ();
  return CDK_General_Error;
}


int
_cdk_keydb_is_secret (cdk_keydb_hd_t db)
{
  return db->secret;
}
